/*
 * MIT License
 *
 * Copyright (c) 2020 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.postman.response.analyzer;

import cn.seaboot.common.reflect.ObjectNode;
import cn.seaboot.common.reflect.ObjectUtils;
import cn.seaboot.postman.PostmanConfiguration;
import cn.seaboot.postman.analyzer.MethodInfo;
import cn.seaboot.postman.response.BodyField;
import io.swagger.v3.oas.annotations.media.Schema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Mr.css
 * @date 2021-01-31 14:29
 */
public class DefaultBodyFieldAnalyzer implements BodyFieldAnalyzer {
    private Logger logger = LoggerFactory.getLogger(DefaultBodyFieldAnalyzer.class);

    /**
     * return a response-field detail information, {{@link BodyField}}
     *
     * @param method method
     * @return 字段描述
     */
    @Override
    public BodyField analyzeBodyField(PostmanConfiguration configuration, MethodInfo method) {
        //使用工具类获取返回值所有有效的成员变量
        ObjectNode objectNode = ObjectUtils.getMethodReturnObjectNodes(method.getMethod());
        if (objectNode == null) {
            return null;
        }
        return this.getResponseByMethod(objectNode);
    }


    /**
     * 根据{@link ObjectNode}生成BodyField
     *
     * @param objectNode -
     * @return -
     */
    private BodyField getResponseByMethod(ObjectNode objectNode) {
        BodyField bodyField = new BodyField();
        bodyField.setObjectType(objectNode.getObjectType());

        //递归遍历所有的子节点
        Map<String, ObjectNode> subNodes = objectNode.getNodes();
        if (!subNodes.isEmpty()) {
            Map<String, BodyField> fields = new HashMap<>();
            for (Map.Entry<String, ObjectNode> entry : subNodes.entrySet()) {
                fields.put(entry.getKey(), this.getResponseByMethod(entry.getValue()));
            }
            bodyField.setFields(fields);
        }

        //如果是成员变量，则解析字段详细信息
        Field field = objectNode.getTarget();
        if (field != null) {
            bodyField.setFieldName(field.getName());
            //获取hibernate-validation的信息
            bodyField.set_validator(this.getSerializedValidator(field));
            //获取字段描述
            this.getSchemaInfo(bodyField, field);
        }
        return bodyField;
    }

    /**
     * Get Schema's info
     *
     * @param bodyField -
     * @param field     -
     */
    private void getSchemaInfo(BodyField bodyField, Field field) {
        Schema schema = field.getAnnotation(Schema.class);
        if (schema != null) {
            bodyField.setExample(schema.example());
            bodyField.setDescription(schema.description());
        }
    }

    /**
     * 序列化validator
     * <p>
     * Serialize hibernate-validator validator, convert validator to HashMap which is serializable.
     * Used java-reflect.
     *
     * @param field field
     * @return null/list
     */
    private List<Map<String, Object>> getSerializedValidator(Field field) {
        Annotation[] annotations = field.getAnnotations();
        List<Map<String, Object>> annotationList = new ArrayList<>();
        for (Annotation annotation : annotations) {
            Class<?> annotationType = annotation.annotationType();
            String str = annotationType.getName();
            //如果注解是hibernate.validator或者javax.validation
            if (str.startsWith("org.hibernate.validator.constraints") || str.startsWith("javax.validation.constraints")) {
                Method[] methods = annotationType.getDeclaredMethods();
                Map<String, Object> map = new HashMap<>(methods.length);
                //将注解名称作为类型
                map.put("_type", annotationType.getSimpleName());
                for (Method method : methods) {
                    try {
                        String methodName = method.getName();
                        //忽略非必要的参数，减少数据量
                        if (isAcceptField(methodName)) {
                            map.put(method.getName(), method.invoke(annotation));
                        }
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        //如果无法解析，则直接忽略
                        logger.error("无法使用Java反射解析响应数据：", e);
                    }
                }
                annotationList.add(map);
            }
        }
        return annotationList;
    }

    /**
     * 忽略hibernate-validation中的这些参数字段
     */
    private static String[] ignoreValidateField = {"message", "groups", "payload"};

    /**
     * Ignore some useless validator field
     *
     * @param fieldName -
     * @return boolean
     */
    private boolean isAcceptField(String fieldName) {
        for (String str : ignoreValidateField) {
            if (str.equals(fieldName)) {
                return false;
            }
        }
        return true;
    }
}
